#include "StdAfx.h"
#include <memory>
#include <algorithm>
#include "AdpcmSessionManager.h"
#include "AdpcmPlayer.h"
#include "AdpcmCapturer.h"

using namespace std;

//--------------------------DATA TYPE DEFINITION--------------------------------

typedef enum AdpcmSessionType
{
	ADPCMSESSIONTYPE_DECODE,
	ADPCMSESSIONTYPE_ENCODE,
} ADPCMSESSIONTYPE;

struct AdpcmSessionInfo 
{
	HAUDIOSESSION sessionId;
	ADPCMSESSIONTYPE type;
	CCritSec lock;
	mutable auto_ptr< AdpcmCapturer > capturer;
	mutable auto_ptr< AdpcmPlayer > player;

	AdpcmSessionInfo() : capturer(NULL), player(NULL) {}

	AdpcmSessionInfo(const AdpcmSessionInfo &other)
	{
		sessionId = other.sessionId;
		type = other.type;
		capturer = other.capturer;
		player = other.player;
	}
};

//----------------------FORWARD DECLARATIONS------------------------------------

static void LockSession(ADPCMSESSIONINFO &sessionInfo);
static void LockStorageSession(SESSIONSTORAGE::value_type &sessionEntry);

//-----------------------STATIC MEMBER------------------------------------------

static bool s_initialized = false;
volatile HAUDIOSESSION AdpcmSessionManager::s_sessionIdSeq = 100000;
CCritSec AdpcmSessionManager::s_sessionIdSeqLock;

//-----------------------STATIC METHODS-----------------------------------------

HAUDIOSESSION AdpcmSessionManager::GetNewSessionId()
{
	CAutoLock lock(&s_sessionIdSeqLock);

	return ++s_sessionIdSeq;
}

int AdpcmSessionManager::GetCaptureDeviceNum()
{
	return AdpcmCapturer::GetCaptureDeviceNum();
}

int AdpcmSessionManager::GetCaptureDeviceName(int deviceIndex, char *buf, int len)
{
	return AdpcmCapturer::GetCaptureDeviceName(deviceIndex, buf, len);
}


//------------------CONSTRUCTOR & DESTRUCTOR------------------------------------

int AdpcmSessionManager::Initialize()
{
	if (s_initialized)
		return ADPCMSDK_SUCCESS;

	CoInitialize(NULL);

	if (AdpcmCapturer::EnumCaptureDevice() < 0)
	{
		CoUninitialize();
		return ADPCMSDK_ERROR_OPERATION_FAILED;
	}

	s_initialized = true;

	return ADPCMSDK_SUCCESS;
}

void AdpcmSessionManager::Uninitialize()
{
	if (!s_initialized)
		return;

	DestroyAllSession();

	AdpcmCapturer::ClearCaptureDevice();

	CoUninitialize();

	s_initialized = false;
}

AdpcmSessionManager::AdpcmSessionManager(void)
{
}


AdpcmSessionManager::~AdpcmSessionManager(void)
{
}

//----------------PUBLIC METHODS------------------------------------------------
HAUDIOSESSION AdpcmSessionManager::CreateCaptureSession(int deviceIndex, int channels, 
	int sampleRate, int bitsPerSample, int bitrate, AudioCodecId codecId, CaptureAudioCallBack callback)
{
	return InternalCreateSession(ADPCMSESSIONTYPE_ENCODE, channels, sampleRate, bitsPerSample, bitrate, codecId, callback, deviceIndex);
}


HAUDIOSESSION AdpcmSessionManager::CreatePlaybackSession(int channels, int sampleRate, int bitsPerSample, AudioCodecId codecId)
{
	return InternalCreateSession(ADPCMSESSIONTYPE_DECODE, channels, sampleRate, bitsPerSample, 0, codecId);
}

int AdpcmSessionManager::PlaybackAdpcmData(HAUDIOSESSION session, char *data, int len)
{
	if (session <= 0 || data == NULL || len <= 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_sessionStorageLock.Lock();

	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
	{
		m_sessionStorageLock.Unlock();

		return ADPCMSDK_ERROR_BAD_SESSION;
	}

	CAutoLock lock(&sessionInfo->lock);
	m_sessionStorageLock.Unlock();

	if (sessionInfo->type != ADPCMSESSIONTYPE_DECODE)
		return ADPCMSDK_ERROR_WRONG_SESSION_TYPE;

	sessionInfo->player->PlaybackAudioData(data, len);

	return ADPCMSDK_SUCCESS;
}

void AdpcmSessionManager::PausePlayAdpcmData(HAUDIOSESSION session)
{
	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	CAutoLock lock(&sessionInfo->lock);

	sessionInfo->player->StopPlay();
}

void AdpcmSessionManager::ResumePlayAdpcmData(HAUDIOSESSION session)
{
	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	CAutoLock lock(&sessionInfo->lock);

	sessionInfo->player->StartPlay();
}

int AdpcmSessionManager::PlaybackAmrData(HAUDIOSESSION session, char *data, int len)
{
	return PlaybackAdpcmData(session, data, len);
}

int AdpcmSessionManager::GetAdpcmPlaybackVolume(HAUDIOSESSION session)
{
	if (session <= 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_sessionStorageLock.Lock();

	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
	{
		m_sessionStorageLock.Unlock();

		return ADPCMSDK_ERROR_BAD_SESSION;
	}

	CAutoLock lock(&sessionInfo->lock);
	m_sessionStorageLock.Unlock();

	if (sessionInfo->type != ADPCMSESSIONTYPE_DECODE)
		return ADPCMSDK_ERROR_WRONG_SESSION_TYPE;

	return sessionInfo->player->GetVolume();
}

int AdpcmSessionManager::SetAdpcmPlaybackVolume(HAUDIOSESSION session, int volume)
{
	if (session <= 0 || volume < 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_sessionStorageLock.Lock();

	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
	{
		m_sessionStorageLock.Unlock();

		return ADPCMSDK_ERROR_BAD_SESSION;
	}

	CAutoLock lock(&sessionInfo->lock);
	m_sessionStorageLock.Unlock();

	if (sessionInfo->type != ADPCMSESSIONTYPE_DECODE)
		return ADPCMSDK_ERROR_WRONG_SESSION_TYPE;

	sessionInfo->player->SetVolume(volume);

	return ADPCMSDK_SUCCESS;
}

int AdpcmSessionManager::GetAudioCaptureVolume(HAUDIOSESSION session)
{
	if (session <= 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_sessionStorageLock.Lock();

	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
	{
		m_sessionStorageLock.Unlock();

		return ADPCMSDK_ERROR_BAD_SESSION;
	}

	CAutoLock lock(&sessionInfo->lock);
	m_sessionStorageLock.Unlock();

	if (sessionInfo->type != ADPCMSESSIONTYPE_ENCODE)
		return ADPCMSDK_ERROR_WRONG_SESSION_TYPE;

	return sessionInfo->capturer->GetVolume();
}

int AdpcmSessionManager::SetAudioCaptureVolume(HAUDIOSESSION session, int volume)
{
	if (session <= 0 || volume < 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_sessionStorageLock.Lock();

	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
	{
		m_sessionStorageLock.Unlock();

		return ADPCMSDK_ERROR_BAD_SESSION;
	}

	CAutoLock lock(&sessionInfo->lock);
	m_sessionStorageLock.Unlock();

	if (sessionInfo->type != ADPCMSESSIONTYPE_ENCODE)
		return ADPCMSDK_ERROR_WRONG_SESSION_TYPE;

	sessionInfo->capturer->SetVolume(volume);

	return ADPCMSDK_SUCCESS;
}

void AdpcmSessionManager::DestroySession(HAUDIOSESSION session)
{
	CAutoLock lock(&m_sessionStorageLock);
	
	ADPCMSESSIONINFO *sessionInfo = GetSessionInfoByHandle(session);
	if (sessionInfo == NULL)
		return;

	LockSession(*sessionInfo);

	m_sessionStorage.erase(session);
}


void AdpcmSessionManager::DestroyAllSession()
{
	CAutoLock lock(&m_sessionStorageLock);

	for_each(m_sessionStorage.begin(), m_sessionStorage.end(), LockStorageSession);
	
	m_sessionStorage.clear();
}

//----------------------------PRIVATE METHODS-----------------------------------

ADPCMSESSIONINFO *AdpcmSessionManager::GetSessionInfoByHandle(HAUDIOSESSION session)
{
	//assume m_sessionStorageLock has been locked already.

	SESSIONSTORAGE::iterator iter = m_sessionStorage.find(session);
	if (iter == m_sessionStorage.end())
		return NULL;

	return &iter->second;
}


HAUDIOSESSION AdpcmSessionManager::InternalCreateSession(ADPCMSESSIONTYPE type, int channels, int sampleRate, int bitsPerSample, int bitrate, AudioCodecId codecId, CaptureAudioCallBack callback, int deviceIndex)
{
	CAutoLock lock(&m_sessionStorageLock);

	HAUDIOSESSION newSessionId = GetNewSessionId();

	ADPCMSESSIONINFO &sessionInfo = m_sessionStorage[newSessionId];
	sessionInfo.type = type;
	sessionInfo.sessionId = newSessionId;

	int errCode = ADPCMSDK_SUCCESS;
	switch (type)
	{
	case ADPCMSESSIONTYPE_DECODE:
		sessionInfo.player = auto_ptr<AdpcmPlayer>(new AdpcmPlayer(channels, sampleRate, bitsPerSample, codecId));
		if (!sessionInfo.player->IsInitialized())
			errCode = ADPCMSDK_ERROR_CREATE_FAILED;
		break;

	case ADPCMSESSIONTYPE_ENCODE:
		sessionInfo.capturer = auto_ptr<AdpcmCapturer>(new AdpcmCapturer(channels, sampleRate, bitsPerSample, bitrate, codecId, callback, deviceIndex, newSessionId));
		if (!sessionInfo.capturer->IsInitialized())
			errCode = ADPCMSDK_ERROR_CREATE_FAILED;
		break;

	default:
		errCode = ADPCMSDK_ERROR_WRONG_SESSION_TYPE;
		break;
	}

	if (errCode != ADPCMSDK_SUCCESS)
	{
		m_sessionStorage.erase(newSessionId);

		return errCode;
	}

	return newSessionId;
}

//--------------------------STATIC FUNCTIONS------------------------------------

static void LockSession(ADPCMSESSIONINFO &sessionInfo)
{
	sessionInfo.lock.Lock();
	sessionInfo.lock.Unlock();
}


static void LockStorageSession(SESSIONSTORAGE::value_type &sessionEntry)
{
	ADPCMSESSIONINFO &sessionInfo = sessionEntry.second;
	LockSession(sessionInfo);
}
